/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Synchronization utils Baseclass.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#ifndef _GIM_UTILS_SYNC_HPP_
#define _GIM_UTILS_SYNC_HPP_
#include "ownutils.h"
#include "owndebug.h"
#include <vector>
#include <assert.h>

#ifdef SYNC_NO_INDEX_CHECK
# warning CSync class does not check for Mutex/Condition lock indexes
#endif

#ifdef SYNC_TRACE_LOCKS
# warning CSync class is tracing Lock/Unlock calls.
#include <typeinfo>
#include <typeinfo>
#endif

namespace gim {

  /** Baseclass implementing some useful Synchronization tools.
   *
   * This class implements multiple Mutexes and Condition variables
   * through a easy to use interface. If you derive your class from this,
   * you can use Mutexes and Condition locks easily without declaring
   * extra variables or doing extra initializations.
   */
  class CSync 
  {
  public:

    /** Constructor.
     *
     * Constructor constructs the required count of slots for mutexes
     * and condition variables.
     * 
     * @param[in] aMaxConditions Maximum number of Conditions available.
     *                           Do not attempt to access more
     *                           conditions than specified here. If
     *                           you do, and the 'SYNC_NO_INDEX_CHECK' is
     *                           enabled, you will probably crash your program.
     *                           if the 'SYNC_NO_INDEX_CHECK' is disabled,
     *                           the software will crash to assert().
     * @param[in] aMaxMutexes    Maximum number of Mutexes available.
     *                           Do not attempt to access more
     *                           Mutexes than specified here. If
     *                           you do, and the 'SYNC_NO_INDEX_CHECK' is
     *                           enabled, you will probably crash your program.
     *                           if the 'SYNC_NO_INDEX_CHECK' is disabled,
     *                           the software will crash to assert().
     */
    CSync(const unsigned int aMaxConditions = 1, const unsigned int aMaxMutexes = 1) 
      : iSyncConditionHandleArray(aMaxConditions, ownCondHandleNullInitializer),
        iSyncMutexHandleArray(aMaxMutexes, ownMutexHandleNullInitializer) 
    {
      // Init all Condition variables
      for (EACH_IN_i(iSyncConditionHandleArray)) {
        *i = ownCond_Init();
        assert(*i != ownCondHandleNullInitializer);
      }

      // Init all Mutex variables
      for (EACH_IN_i(iSyncMutexHandleArray)) {
        *i = ownMutex_Init();
        assert(*i != ownMutexHandleNullInitializer);
      }
      dPrint(8,"CSync %p constructed", this);
    }
    

    /** Destructor.
     *
     * The destructor takes care of freeing all the constructed Mutexes
     * and Conditions.
     */
    virtual ~CSync() {
      // Destroy all Condition variables
      for (EACH_IN_i(iSyncConditionHandleArray)) {
        ownCondHandle h = *i;
        if (h != NULL) ownCond_Destroy(h);
        *i = NULL;
      }

      // Destroy all Mutex variables
      for (EACH_IN_i(iSyncMutexHandleArray)) {
        ownMutexHandle h = *i;
        if (h != NULL) ownMutex_Destroy(h);
        *i = NULL;
      }
      
      iSyncConditionHandleArray.clear();
      iSyncMutexHandleArray.clear();

      dPrint(8,"CSync %p destructed", this);
    }


#ifdef SYNC_TRACE_LOCKS
    std::string DeMangleClass(const std::string &aMangled) {
      char buf[512] = "Unknown ";
      std::string cmd = "/usr/bin/c++filt -t " + aMangled;
      FILE *f = popen(cmd.c_str(), "r");
      if (f) {
        fgets(buf, sizeof(buf), f);
        int len = strlen(buf);
        buf[len-1] = '\0';
        fclose(f);
      }
      return std::string(buf);
    }
#endif

    /** Lock Mutex.
     *
     * This functions Locks a Mutex. If mutex number is omitted, it defaults to
     * 0 mutex. If you use multiple Mutexes, be sure not to use index too high,
     * otherwise you will face serious trouble.
     *
     * @param[in] aMutexId      Mutex index to lock. (0 is the first mutex,
     *                          highest ID available is the specified
     *                          ('aMaxMutexes' - 1) in the constructor.
     */
    void Lock(const unsigned int aMutexId = 0) const
    {
#ifndef SYNC_NO_INDEX_CHECK
      assert(aMutexId < iSyncMutexHandleArray.size());
#endif	
      // Get a easier-to-use pointer.
      ownMutexHandle m = iSyncMutexHandleArray[aMutexId];

#ifdef SYNC_TRACE_LOCKS
      dPrint(ODTEST,"ownMutex_Lock(%p) by thread %u (Class '%s')",
             m, pthread_self(), DeMangleClass(typeid(*this).name()).c_str());
#endif
      ownMutex_Lock(m);
    }

    /** Try locking a Mutex.
     *
     * This functions attempts to lock a Mutex, but doesn't block if
     * locking immediately fails.  If mutex number is omitted, it
     * defaults to 0 mutex. If you use multiple Mutexes, be sure not
     * to use index too high, otherwise you will face serious trouble.
     *
     * \note This function may return before acquiring a mutex, so return
     * value from this function must ALWAYS be checked!
     *
     * @param[in] aMutexId      Mutex index to lock. (0 is the first mutex,
     *                          highest ID available is the specified
     *                          ('aMaxMutexes' - 1) in the constructor.
     * @return                  'true' if the mutex was succesfully locked,
     *                          'false' in case the mutex was Busy. (Could not
     *                          lock immediately)
     */
    bool TryLock(const unsigned int aMutexId = 0) const
    {
#ifndef SYNC_NO_INDEX_CHECK
      assert(aMutexId < iSyncMutexHandleArray.size());
#endif	
      // Get a easier-to-use pointer.
      ownMutexHandle m = iSyncMutexHandleArray[aMutexId];
      
#ifdef SYNC_TRACE_LOCKS
      dPrint(ODTEST,"ownMutex_TryLock(%p) by thread %u",
             m, pthread_self());
#endif
      return ownMutex_TryLock(m) == ownStatusOK;
    }


    /** Lock all mutexes in given Array.
     *
     * \note Experimental Mutex locking function.
     *
     */
    void LockAll(const std::vector<unsigned int> aMutexIdArray) {
      std::vector<unsigned int> acquiredLocks;
      acquiredLocks.reserve(aMutexIdArray.size());

      

      do {
        for(EACH_IN_i(aMutexIdArray)) {
          //          dPrint(ODTEST,"Trying %u (%p)", *i, iSyncMutexHandleArray[*i]);
          const bool r = TryLock(*i);
          if (r) {
            // Lock ok, push to acquired array.
            acquiredLocks.push_back(*i);
            //dPrint(ODTEST,"Acquired %u (%p) - OK", *i, iSyncMutexHandleArray[*i]);

          } else {
            // Some lock failed: too bad, must start over again.
            //dPrint(ODTEST,"Lock %u (%p) failed!", *i, iSyncMutexHandleArray[*i]);
            for(EACH_IN_j(acquiredLocks)) {
              //dPrint(ODTEST,"Unlocking %u (%p)", *i, iSyncMutexHandleArray[*i]);
              Unlock(*j);
            }
            acquiredLocks.clear();
            // And finally, break from the for(EACH...) loop.
            break;

          }

        }
      } while(acquiredLocks.size() < aMutexIdArray.size());

    }


    /** UnLock all mutexes in given Array.
     *
     * \note Experimental Mutex locking function.
     *
     */
    void UnlockAll(const std::vector<unsigned int> aMutexIdArray) const
    {
      for(EACH_IN_i(aMutexIdArray)) {
        Unlock(*i);
      }
    }
    
    
    /** Unlock Mutex.
     *
     * This functions Unlocks a Mutex. If mutex number is omitted, it defaults to
     * 0 mutex. If you use multiple Mutexes, be sure not to use index too high,
     * otherwise you will face serious trouble.
     *
     * @param[in] aMutexId      Mutex index to Unlock. (0 is the first mutex,
     *                          highest ID available is the specified
     *                          ('aMaxMutexes' - 1) in the constructor.
     */
    void Unlock(const unsigned int aMutexId = 0) const
    {
#ifndef SYNC_NO_INDEX_CHECK
      assert(aMutexId < iSyncMutexHandleArray.size());
#endif	

      ownMutexHandle m = iSyncMutexHandleArray[aMutexId];
      
#ifdef SYNC_TRACE_LOCKS
      dPrint(ODTEST,"ownMutex_Unlock(%p) by thread %u (Class '%s')",
             m, pthread_self(), DeMangleClass(typeid(*this).name()).c_str());
#endif
      
      ownMutex_Unlock(m);
    }
    
    
    /** Notify Condition/Lock.
     *
     * This function signals the specified Condition. If the condition number is omitted,
     * it defaults to 0. Two methods are supported; Signaling and Broadcasting. 
     * 
     * @param[in] aId           Condition to signal. (0 is the first condition,
     *                          highest ID available is the specified
     *                          ('aMaxConditions' - 1) in the constructor.
     * @param[in] aNotifyAll    If this is 'true', the Condition is broadcasted
     *                          (all waiting threads are notified), otherwise
     *                          (false) only one waiting thread is notified
     *                          (thread to be waken can not be determined, it is
     *                          random in practice)
     * @return                  'true' if the signaling was succesfull, 
     *                          otherwise 'false'           
     */
    bool Notify(const unsigned int aId = 0, 
                const bool aNotifyAll = true)
    {
#ifndef SYNC_NO_INDEX_CHECK
      // Check that the ID is within Range
      assert(aId < iSyncConditionHandleArray.size());
#endif	
      // Broadcast the event
      if (aNotifyAll) {
        return (ownCond_Broadcast(iSyncConditionHandleArray[aId]) == ownStatusOK);
      } else {
        return (ownCond_Signal(iSyncConditionHandleArray[aId]) == ownStatusOK);
      }
    }
    
    
    /** Wait for Condition/Lock.
     *
     * This function Unlocks the mutex and enters the calling thread in sleeping
     * state until a Signal is received or timeout occures. When signal is received, 
     * or timeout has occured, the Mutex is reaquired before control is returned to 
     * caller (Means; before this functions returns) 
     *
     * <b> NOTE: The calling thread must have previously Lock():ed 
     *     the same Mutex as this Wait is called with!</b>
     *
     * @param[in] aTimeout_ms   Timeout in milliseconds to wait before giving up.
     * @param[in] aId           Condition number to wait. (0 is the first condition,
     *                          highest ID available is the specified
     *                          ('aMaxConditions' - 1) in the constructor.
     * @param[in] aMutexId      Mutex number to use for atomic operation. 
     *                          (0 is the first mutex, highest ID available 
     *                          is the specified ('aMaxMutexes' - 1) 
     *                          in the constructor.
     * @return                  'true' - When the Wait() received Signal before
     *                          exiting. 
     *                          'false' when Timeout occured before signal was
     *                          received.
     */
    bool Wait(const int aTimeout_ms = -1, 
              const unsigned int aId = 0, 
              const unsigned int aMutexId = 0)
    {
#ifndef SYNC_NO_INDEX_CHECK
      // Check that the ID is within Range
      assert(aMutexId < iSyncMutexHandleArray.size());
      assert(aId < iSyncConditionHandleArray.size());
#endif	
      // Now, start waiting on the condition wait for given time
      return ownCond_Wait(iSyncConditionHandleArray[aId], 
                          iSyncMutexHandleArray[aMutexId], 
                          aTimeout_ms) == ownStatusOK;
    }
    
  private:
    mutable std::vector<ownCondHandle> iSyncConditionHandleArray; ///< Array of conditions
    mutable std::vector<ownMutexHandle> iSyncMutexHandleArray; ///< Array of mutexes
    
  private:
    /// Private constructor to prevent copying this class.
    /// \todo Think whether we need to support copying CSync instances?
    CSync(const CSync &)
      : iSyncConditionHandleArray(),
        iSyncMutexHandleArray() {}
    /// Private '=' operator to prevent copying this class.
    CSync &operator=(const CSync&) { return *this; }
    
  };
};

#endif
